/*********************************************************
* Copyright (C) 1999-2016 VMware, Inc. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation version 2.1 and no later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE.  See the Lesser GNU General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA.
*
*********************************************************/

/*********************************************************
* The contents of this file are subject to the terms of the Common
* Development and Distribution License (the "License") version 1.0
* and no later version.  You may not use this file except in
* compliance with the License.
*
* You can obtain a copy of the License at
*         http://www.opensource.org/licenses/cddl1.php
*
* See the License for the specific language governing permissions
* and limitations under the License.
*
*********************************************************/

/*
* message.c --
*
*    Second layer of the internal communication channel between guest
*    applications and vmware
*
*    Build a generic messaging system between guest applications and vmware.
*
*    The protocol is not completely symmetrical, because:
*     . basic requests can only be sent by guest applications (when vmware
*       wants to post a message to a guest application, the message will be
*       really fetched only when the guest application will poll for new
*       available messages)
*     . several guest applications can talk to vmware, while the contrary is
*       not true
*
*    Operations that are not atomic (in terms of number of backdoor calls)
*    can be aborted by vmware if a checkpoint/restore occurs in the middle of
*    such an operation. This layer takes care of retrying those operations.
*/

#ifdef __cplusplus
extern "C" {
#endif

#if defined(__KERNEL__) || defined(_KERNEL) || defined(KERNEL)
#   include "kernelStubs.h"
#else
#   include <stdio.h>
#   include <stdlib.h>
#endif

#include "backdoor_def.h"
#include "guest_msg_def.h"
#include "backdoor.h"
#include "message.h"

#define _CONCRT_ASSERT(x) __assume(x)
#define ASSERT(x) do {_CONCRT_ASSERT(x); __assume(x);} while(false)
#if defined(MESSAGE_DEBUG)
#  define MESSAGE_LOG(...)   Warning(__VA_ARGS__)
#else
#  define MESSAGE_LOG(...)
#endif
	/*
	*-----------------------------------------------------------------------------
	*
	* Message_OpenAllocated --
	*
	*    Open a communication channel using an allocated, but unitialized
	*    Message_Channel structure.  A receive buffer may be optionally
	*    specified with a given size.  If a message larger than this
	*    buffer is received the communication will be aborted.  If no
	*    receiver buffer is specified, one will be dynamically allocated
	*    to size.  When finished with the channel, Message_CloseAllocated
	*    should be called.
	*
	* Result:
	*    TRUE on success, FALSE on failure.
	*
	* Side-effects:
	*    See above.
	*
	*-----------------------------------------------------------------------------
	*/
#pragma warning( push )
#pragma warning( disable : 4731) 
	void /*__declspec(naked)*/ BackdoorHbIn(Backdoor_proto_hb *myBp) // IN/OUT
	{
		__asm{
			mov eax, myBp;
			push ebp;
			push eax;
			mov ebp, [eax + 24];
			mov edi, [eax + 20];
			mov esi, [eax + 16];
			mov edx, [eax + 12];
			mov ecx, [eax + 8];
			mov ebx, [eax + 4];
			mov eax, [eax];
			cld;
			rep insb;
			xchg[esp], eax;
			mov[eax + 24], ebp;
			mov[eax + 20], edi;
			mov[eax + 16], esi;
			mov[eax + 12], edx;
			mov[eax + 8], ecx;
			mov[eax + 4], ebx;
			pop dword ptr [eax];
			pop ebp;
		};
	}


	void /*__declspec(naked)*/ BackdoorHbOut(Backdoor_proto_hb *myBp) // IN/OUT
	{

		__asm{

			mov eax, myBp;
			push ebp;
			push eax;
			mov ebp, [eax + 24];
			mov edi, [eax + 20];
			mov esi, [eax + 16];
			mov edx, [eax + 12];
			mov ecx, [eax + 8];
			mov ebx, [eax + 4];
			mov eax, [eax];
			cld;
			rep outsb;
			xchg[esp], eax;
			mov[eax + 24], ebp;
			mov[eax + 20], edi;
			mov[eax + 16], esi;
			mov[eax + 12], edx;
			mov[eax + 8], ecx;
			mov[eax + 4], ebx;
			pop dword ptr [eax];
			pop ebp;
		};
	}
#pragma warning( pop )
	void
		Backdoor_HbIn(Backdoor_proto_hb *myBp) // IN/OUT
	{
			ASSERT(myBp);

			myBp->in.ax.word = BDOOR_MAGIC;
			myBp->in.dx.halfs.low = BDOORHB_PORT;


#ifdef USE_VALGRIND
			VALGRIND_NON_SIMD_CALL1(BackdoorHbInValgrind, myBp);
#else
			BackdoorHbIn(myBp);
#endif

		}

	void
		Backdoor_HbOut(Backdoor_proto_hb *myBp) // IN/OUT
	{
			ASSERT(myBp);

			myBp->in.ax.word = BDOOR_MAGIC;
			myBp->in.dx.halfs.low = BDOORHB_PORT;

#ifdef USE_VALGRIND
			VALGRIND_NON_SIMD_CALL1(BackdoorHbOutValgrind, myBp);
#else
			BackdoorHbOut(myBp);
#endif
		}

	void Backdoor_InOut(Backdoor_proto *myBp) // IN/OUT
	{
		_asm {
			mov eax, myBp;
			push eax;
			mov edi, [eax + 20];
			mov esi, [eax + 16];
			mov edx, [eax + 12];
			mov ecx, [eax + 8];
			mov ebx, [eax + 4];
			mov eax, [eax];
			in eax, dx;
			xchg[esp], eax;
			mov[eax + 20], edi;
			mov[eax + 16], esi;
			mov[eax + 12], edx;
			mov[eax + 8], ecx;
			mov[eax + 4], ebx;
			pop dword ptr [eax];
		};
	}

	void Backdoor(Backdoor_proto *myBp) // IN/OUT
	{

		myBp->in.ax.word = BDOOR_MAGIC;
		myBp->in.dx.halfs.low = BDOOR_PORT;

		Backdoor_InOut(myBp);
	}
	Bool
		Message_OpenAllocated(uint32 proto, Message_Channel *chan,
		char *receiveBuffer, size_t receiveBufferSize)
	{
			uint32 flags;
			Backdoor_proto bp;

			flags = GUESTMSG_FLAG_COOKIE;
		retry:
			/* IN: Type */
			bp.in.cx.halfs.high = MESSAGE_TYPE_OPEN;
			/* IN: Magic number of the protocol and flags */
			bp.in.size = proto | flags;

			bp.in.cx.halfs.low = BDOOR_CMD_MESSAGE;
			Backdoor(&bp);

			/* OUT: Status */
			if ((bp.in.cx.halfs.high & MESSAGE_STATUS_SUCCESS) == 0) {
				if (flags) {
					/* Cookies not supported. Fall back to no cookie. --hpreg */
					flags = 0;
					goto retry;
				}

				MESSAGE_LOG("Message: Unable to open a communication channel\n");
				return FALSE;
			}

			/* OUT: Id and cookie */
			chan->id = bp.in.dx.halfs.high;
			chan->cookieHigh = bp.out.si.word;
			chan->cookieLow = bp.out.di.word;

			/* Initialize the channel */
			chan->in = (unsigned char *)receiveBuffer;
			chan->inAlloc = receiveBufferSize;

			ASSERT((receiveBuffer == NULL) == (receiveBufferSize == 0));
			chan->inPreallocated = receiveBuffer != NULL;

			return TRUE;
		}


	/*
	*-----------------------------------------------------------------------------
	*
	* Message_Open --
	*
	*    Open a communication channel
	*
	* Result:
	*    An allocated Message_Channel on success
	*    NULL on failure
	*
	* Side-effects:
	*    None
	*
	*-----------------------------------------------------------------------------
	*/

	Message_Channel *
		Message_Open(uint32 proto) // IN
	{
			Message_Channel *chan = (Message_Channel*)malloc(sizeof *chan);

			if (chan != NULL && !Message_OpenAllocated(proto, chan, NULL, 0)) {
				free(chan);
				chan = NULL;
			}
			return chan;
		}


	/*
	*-----------------------------------------------------------------------------
	*
	* Message_Send --
	*
	*    Send a message over a communication channel
	*
	* Result:
	*    TRUE on success
	*    FALSE on failure (the message is discarded by vmware)
	*
	* Side-effects:
	*    None
	*
	*-----------------------------------------------------------------------------
	*/

	Bool
		Message_Send(Message_Channel *chan,    // IN/OUT
		const unsigned char *buf, // IN
		size_t bufSize)           // IN
	{
			const unsigned char *myBuf;
			size_t myBufSize;
			Backdoor_proto bp;

		retry:
			myBuf = buf;
			myBufSize = bufSize;

			/*
			* Send the size.
			*/

			/* IN: Type */
			bp.in.cx.halfs.high = MESSAGE_TYPE_SENDSIZE;
			/* IN: Id and cookie */
			bp.in.dx.halfs.high = chan->id;
			bp.in.si.word = chan->cookieHigh;
			bp.in.di.word = chan->cookieLow;
			/* IN: Size */
			bp.in.size = myBufSize;

			bp.in.cx.halfs.low = BDOOR_CMD_MESSAGE;
			Backdoor(&bp);

			/* OUT: Status */
			if ((bp.in.cx.halfs.high & MESSAGE_STATUS_SUCCESS) == 0) {
				MESSAGE_LOG("Message: Unable to send a message over the communication "
					"channel %u\n", chan->id);
				return FALSE;
			}

			if (bp.in.cx.halfs.high & MESSAGE_STATUS_HB) {
				/*
				* High-bandwidth backdoor port supported. Send the message in one
				* backdoor operation. --hpreg
				*/

				if (myBufSize) {
					Backdoor_proto_hb bphb;

					bphb.in.bx.halfs.low = BDOORHB_CMD_MESSAGE;
					bphb.in.bx.halfs.high = MESSAGE_STATUS_SUCCESS;
					bphb.in.dx.halfs.high = chan->id;
					bphb.in.bp.word = chan->cookieHigh;
					bphb.in.dstAddr = chan->cookieLow;
					bphb.in.size = myBufSize;
					bphb.in.srcAddr = (uintptr_t)myBuf;
					Backdoor_HbOut(&bphb);
					if ((bphb.in.bx.halfs.high & MESSAGE_STATUS_SUCCESS) == 0) {
						if ((bphb.in.bx.halfs.high & MESSAGE_STATUS_CPT) != 0) {
							/* A checkpoint occurred. Retry the operation. --hpreg */
							goto retry;
						}

						MESSAGE_LOG("Message: Unable to send a message over the "
							"communication channel %u\n", chan->id);
						return FALSE;
					}
				}
			}
			else {
				/*
				* High-bandwidth backdoor port not supported. Send the message, 4 bytes
				* at a time. --hpreg
				*/

				for (;;) {
					if (myBufSize == 0) {
						/* We are done */
						break;
					}

					/* IN: Type */
					bp.in.cx.halfs.high = MESSAGE_TYPE_SENDPAYLOAD;
					/* IN: Id and cookie */
					bp.in.dx.halfs.high = chan->id;
					bp.in.si.word = chan->cookieHigh;
					bp.in.di.word = chan->cookieLow;
					/* IN: Piece of message */
					/*
					* Beware in case we are not allowed to read extra bytes beyond the
					* end of the buffer.
					*/
					switch (myBufSize) {
					case 1:
						bp.in.size = myBuf[0];
						myBufSize -= 1;
						break;
					case 2:
						bp.in.size = myBuf[0] | myBuf[1] << 8;
						myBufSize -= 2;
						break;
					case 3:
						bp.in.size = myBuf[0] | myBuf[1] << 8 | myBuf[2] << 16;
						myBufSize -= 3;
						break;
					default:
						bp.in.size = *(const uint32 *)myBuf;
						myBufSize -= 4;
						break;
					}

					bp.in.cx.halfs.low = BDOOR_CMD_MESSAGE;
					Backdoor(&bp);

					/* OUT: Status */
					if ((bp.in.cx.halfs.high & MESSAGE_STATUS_SUCCESS) == 0) {
						if ((bp.in.cx.halfs.high & MESSAGE_STATUS_CPT) != 0) {
							/* A checkpoint occurred. Retry the operation. --hpreg */
							goto retry;
						}

						MESSAGE_LOG("Message: Unable to send a message over the "
							"communication channel %u\n", chan->id);
						return FALSE;
					}

					myBuf += 4;
				}
			}

			return TRUE;
		}


	/*
	*-----------------------------------------------------------------------------
	*
	* Message_Receive --
	*
	*    If vmware has posted a message for this channel, retrieve it
	*
	* Result:
	*    TRUE on success (bufSize is 0 if there is no message)
	*    FALSE on failure
	*
	* Side-effects:
	*    None
	*
	*-----------------------------------------------------------------------------
	*/

	Bool
		Message_Receive(Message_Channel *chan, // IN/OUT
		unsigned char **buf,   // OUT
		size_t *bufSize)       // OUT
	{
			Backdoor_proto bp;
			size_t myBufSize;
			unsigned char *myBuf;

		retry:
			/*
			* Is there a message waiting for our retrieval?
			*/

			/* IN: Type */
			bp.in.cx.halfs.high = MESSAGE_TYPE_RECVSIZE;
			/* IN: Id and cookie */
			bp.in.dx.halfs.high = chan->id;
			bp.in.si.word = chan->cookieHigh;
			bp.in.di.word = chan->cookieLow;

			bp.in.cx.halfs.low = BDOOR_CMD_MESSAGE;
			Backdoor(&bp);

			/* OUT: Status */
			if ((bp.in.cx.halfs.high & MESSAGE_STATUS_SUCCESS) == 0) {
				MESSAGE_LOG("Message: Unable to poll for messages over the "
					"communication channel %u\n", chan->id);
				return FALSE;
			}

			if ((bp.in.cx.halfs.high & MESSAGE_STATUS_DORECV) == 0) {
				/* No message to retrieve */
				*bufSize = 0;
				return TRUE;
			}

			/*
			* Receive the size.
			*/

			/* OUT: Type */
			if (bp.in.dx.halfs.high != MESSAGE_TYPE_SENDSIZE) {
				MESSAGE_LOG("Message: Protocol error. Expected a "
					"MESSAGE_TYPE_SENDSIZE request from vmware\n");
				return FALSE;
			}

			/* OUT: Size */
			myBufSize = bp.out.bx.word;

			/*
			* Allocate an extra byte for a trailing NUL character. The code that will
			* deal with this message may not know about binary strings, and may expect
			* a C string instead. --hpreg
			*/
			if (myBufSize + 1 > chan->inAlloc) {
				if (chan->inPreallocated) {
					MESSAGE_LOG("Message: Buffer too small to receive a message over "
						"the communication channel %u\n", chan->id);
					goto error_quit;
				}
				else {
					myBuf = (unsigned char *)realloc(chan->in, myBufSize + 1);
					if (myBuf == NULL) {
						MESSAGE_LOG("Message: Not enough memory to receive a message over "
							"the communication channel %u\n", chan->id);
						goto error_quit;
					}
					chan->in = myBuf;
					chan->inAlloc = myBufSize + 1;
				}
			}
			*bufSize = myBufSize;
			myBuf = *buf = chan->in;

			if (bp.in.cx.halfs.high & MESSAGE_STATUS_HB) {
				/*
				* High-bandwidth backdoor port supported. Receive the message in one
				* backdoor operation. --hpreg
				*/

				if (myBufSize) {
					Backdoor_proto_hb bphb;

					bphb.in.bx.halfs.low = BDOORHB_CMD_MESSAGE;
					bphb.in.bx.halfs.high = MESSAGE_STATUS_SUCCESS;
					bphb.in.dx.halfs.high = chan->id;
					bphb.in.srcAddr = chan->cookieHigh;
					bphb.in.bp.word = chan->cookieLow;
					bphb.in.size = myBufSize;
					bphb.in.dstAddr = (uintptr_t)myBuf;
					Backdoor_HbIn(&bphb);
					if ((bphb.in.bx.halfs.high & MESSAGE_STATUS_SUCCESS) == 0) {
						if ((bphb.in.bx.halfs.high & MESSAGE_STATUS_CPT) != 0) {
							/* A checkpoint occurred. Retry the operation. --hpreg */
							goto retry;
						}

						MESSAGE_LOG("Message: Unable to receive a message over the "
							"communication channel %u\n", chan->id);
						goto error_quit;
					}
				}
			}
			else {
				/*
				* High-bandwidth backdoor port not supported. Receive the message, 4
				* bytes at a time. --hpreg
				*/

				for (;;) {
					if (myBufSize == 0) {
						/* We are done */
						break;
					}

					/* IN: Type */
					bp.in.cx.halfs.high = MESSAGE_TYPE_RECVPAYLOAD;
					/* IN: Id and cookie */
					bp.in.dx.halfs.high = chan->id;
					bp.in.si.word = chan->cookieHigh;
					bp.in.di.word = chan->cookieLow;
					/* IN: Status for the previous request (that succeeded) */
					bp.in.size = MESSAGE_STATUS_SUCCESS;

					bp.in.cx.halfs.low = BDOOR_CMD_MESSAGE;
					Backdoor(&bp);

					/* OUT: Status */
					if ((bp.in.cx.halfs.high & MESSAGE_STATUS_SUCCESS) == 0) {
						if ((bp.in.cx.halfs.high & MESSAGE_STATUS_CPT) != 0) {
							/* A checkpoint occurred. Retry the operation. --hpreg */
							goto retry;
						}

						MESSAGE_LOG("Message: Unable to receive a message over the "
							"communication channel %u\n", chan->id);
						goto error_quit;
					}

					/* OUT: Type */
					if (bp.in.dx.halfs.high != MESSAGE_TYPE_SENDPAYLOAD) {
						MESSAGE_LOG("Message: Protocol error. Expected a "
							"MESSAGE_TYPE_SENDPAYLOAD from vmware\n");
						goto error_quit;
					}

					/* OUT: Piece of message */
					/*
					* Beware in case we are not allowed to write extra bytes beyond the
					* end of the buffer. --hpreg
					*/
					switch (myBufSize) {
					case 1:
						myBuf[0] = bp.out.bx.word & 0xff;
						myBufSize -= 1;
						break;
					case 2:
						myBuf[0] = bp.out.bx.word & 0xff;
						myBuf[1] = (bp.out.bx.word >> 8) & 0xff;
						myBufSize -= 2;
						break;
					case 3:
						myBuf[0] = bp.out.bx.word & 0xff;
						myBuf[1] = (bp.out.bx.word >> 8) & 0xff;
						myBuf[2] = (bp.out.bx.word >> 16) & 0xff;
						myBufSize -= 3;
						break;
					default:
						*(uint32 *)myBuf = bp.out.bx.word;
						myBufSize -= 4;
						break;
					}

					myBuf += 4;
				}
			}

			/* Write a trailing NUL just after the message. --hpreg */
			chan->in[*bufSize] = '\0';

			/* IN: Type */
			bp.in.cx.halfs.high = MESSAGE_TYPE_RECVSTATUS;
			/* IN: Id and cookie */
			bp.in.dx.halfs.high = chan->id;
			bp.in.si.word = chan->cookieHigh;
			bp.in.di.word = chan->cookieLow;
			/* IN: Status for the previous request (that succeeded) */
			bp.in.size = MESSAGE_STATUS_SUCCESS;

			bp.in.cx.halfs.low = BDOOR_CMD_MESSAGE;
			Backdoor(&bp);

			/* OUT: Status */
			if ((bp.in.cx.halfs.high & MESSAGE_STATUS_SUCCESS) == 0) {
				if ((bp.in.cx.halfs.high & MESSAGE_STATUS_CPT) != 0) {
					/* A checkpoint occurred. Retry the operation. --hpreg */
					goto retry;
				}

				MESSAGE_LOG("Message: Unable to receive a message over the "
					"communication channel %u\n", chan->id);
				goto error_quit;
			}

			return TRUE;

		error_quit:
			/* IN: Type */
			if (myBufSize == 0) {
				bp.in.cx.halfs.high = MESSAGE_TYPE_RECVSTATUS;
			}
			else {
				bp.in.cx.halfs.high = MESSAGE_TYPE_RECVPAYLOAD;
			}
			/* IN: Id and cookie */
			bp.in.dx.halfs.high = chan->id;
			bp.in.si.word = chan->cookieHigh;
			bp.in.di.word = chan->cookieLow;
			/* IN: Status for the previous request (that failed) */
			bp.in.size = 0;

			bp.in.cx.halfs.low = BDOOR_CMD_MESSAGE;
			Backdoor(&bp);

			/* OUT: Status */
			if ((bp.in.cx.halfs.high & MESSAGE_STATUS_SUCCESS) == 0) {
				MESSAGE_LOG("Message: Unable to signal an error of reception over the "
					"communication channel %u\n", chan->id);
				return FALSE;
			}

			return FALSE;
		}


	/*
	*-----------------------------------------------------------------------------
	*
	* Message_CloseAllocated --
	*
	*    Close a communication channel that had been allocated by the
	*    caller.  (For use with Message_OpenAllocated.)
	*
	* Result:
	*    TRUE on success, the channel is destroyed
	*    FALSE on failure
	*
	* Side-effects:
	*    None
	*
	*-----------------------------------------------------------------------------
	*/

	Bool
		Message_CloseAllocated(Message_Channel *chan) // IN/OUT
	{
			Backdoor_proto bp;
			Bool ret = TRUE;

			/* IN: Type */
			bp.in.cx.halfs.high = MESSAGE_TYPE_CLOSE;
			/* IN: Id and cookie */
			bp.in.dx.halfs.high = chan->id;
			bp.in.si.word = chan->cookieHigh;
			bp.in.di.word = chan->cookieLow;

			bp.in.cx.halfs.low = BDOOR_CMD_MESSAGE;
			Backdoor(&bp);

			/* OUT: Status */
			if ((bp.in.cx.halfs.high & MESSAGE_STATUS_SUCCESS) == 0) {
				MESSAGE_LOG("Message: Unable to close the communication channel %u\n",
					chan->id);
				ret = FALSE;
			}

			if (!chan->inPreallocated) {
				free(chan->in);
			}
			chan->in = NULL;

			return ret;
		}


	/*
	*-----------------------------------------------------------------------------
	*
	* Message_Close --
	*
	*    Close a communication channel.
	*
	* Result:
	*    TRUE on success, the channel is destroyed
	*    FALSE on failure
	*
	* Side-effects:
	*    None
	*
	*-----------------------------------------------------------------------------
	*/

	Bool
		Message_Close(Message_Channel *chan) // IN/OUT
	{
			Bool ret = Message_CloseAllocated(chan);

			free(chan);
			return ret;
		}

#ifdef __cplusplus
}
#endif
